// Parallax Occlusion shaders from
//    http://sunandblackcat.com/tipFullView.php?topicid=28
// No tangent-space transforms logic based on
//   http://mmikkelsen3d.blogspot.sk/2012/02/parallaxpoc-mapping-and-no-tangent.html

export const ParallaxShader = {
  // Ordered from fastest to best quality.
  modes: {
    none: 'NO_PARALLAX',
    basic: 'USE_BASIC_PARALLAX',
    steep: 'USE_STEEP_PARALLAX',
    occlusion: 'USE_OCLUSION_PARALLAX', // a.k.a. POM
    relief: 'USE_RELIEF_PARALLAX',
  },

  uniforms: {
    bumpMap: { value: null },
    map: { value: null },
    parallaxScale: { value: null },
    parallaxMinLayers: { value: null },
    parallaxMaxLayers: { value: null },
  },

  vertexShader: /* glsl */ `
    varying vec2 vUv;
    varying vec3 vViewPosition;
    varying vec3 vNormal;

    void main() {

    	vUv = uv;
    	vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
    	vViewPosition = -mvPosition.xyz;
    	vNormal = normalize( normalMatrix * normal );
    	gl_Position = projectionMatrix * mvPosition;

    }
  `,

  fragmentShader: /* glsl */ `
    uniform sampler2D bumpMap;
    uniform sampler2D map;

    uniform float parallaxScale;
    uniform float parallaxMinLayers;
    uniform float parallaxMaxLayers;

    varying vec2 vUv;
    varying vec3 vViewPosition;
    varying vec3 vNormal;

    #ifdef USE_BASIC_PARALLAX

    	vec2 parallaxMap( in vec3 V ) {

    		float initialHeight = texture2D( bumpMap, vUv ).r;

    // No Offset Limitting: messy, floating output at grazing angles.
    //vec2 texCoordOffset = parallaxScale * V.xy / V.z * initialHeight;

    // Offset Limiting
    		vec2 texCoordOffset = parallaxScale * V.xy * initialHeight;
    		return vUv - texCoordOffset;

    	}

    #else

    	vec2 parallaxMap( in vec3 V ) {

    // Determine number of layers from angle between V and N
    		float numLayers = mix( parallaxMaxLayers, parallaxMinLayers, abs( dot( vec3( 0.0, 0.0, 1.0 ), V ) ) );

    		float layerHeight = 1.0 / numLayers;
    		float currentLayerHeight = 0.0;
    // Shift of texture coordinates for each iteration
    		vec2 dtex = parallaxScale * V.xy / V.z / numLayers;

    		vec2 currentTextureCoords = vUv;

    		float heightFromTexture = texture2D( bumpMap, currentTextureCoords ).r;

    // while ( heightFromTexture > currentLayerHeight )
    // Infinite loops are not well supported. Do a "large" finite
    // loop, but not too large, as it slows down some compilers.
    		for ( int i = 0; i < 30; i += 1 ) {
    			if ( heightFromTexture <= currentLayerHeight ) {
    				break;
    			}
    			currentLayerHeight += layerHeight;
    // Shift texture coordinates along vector V
    			currentTextureCoords -= dtex;
    			heightFromTexture = texture2D( bumpMap, currentTextureCoords ).r;
    		}

    		#ifdef USE_STEEP_PARALLAX

    			return currentTextureCoords;

    		#elif defined( USE_RELIEF_PARALLAX )

    			vec2 deltaTexCoord = dtex / 2.0;
    			float deltaHeight = layerHeight / 2.0;

    // Return to the mid point of previous layer
    			currentTextureCoords += deltaTexCoord;
    			currentLayerHeight -= deltaHeight;

    // Binary search to increase precision of Steep Parallax Mapping
    			const int numSearches = 5;
    			for ( int i = 0; i < numSearches; i += 1 ) {

    				deltaTexCoord /= 2.0;
    				deltaHeight /= 2.0;
    				heightFromTexture = texture2D( bumpMap, currentTextureCoords ).r;
    // Shift along or against vector V
    				if( heightFromTexture > currentLayerHeight ) { // Below the surface

    					currentTextureCoords -= deltaTexCoord;
    					currentLayerHeight += deltaHeight;

    				} else { // above the surface

    					currentTextureCoords += deltaTexCoord;
    					currentLayerHeight -= deltaHeight;

    				}

    			}
    			return currentTextureCoords;

    		#elif defined( USE_OCLUSION_PARALLAX )

    			vec2 prevTCoords = currentTextureCoords + dtex;

    // Heights for linear interpolation
    			float nextH = heightFromTexture - currentLayerHeight;
    			float prevH = texture2D( bumpMap, prevTCoords ).r - currentLayerHeight + layerHeight;

    // Proportions for linear interpolation
    			float weight = nextH / ( nextH - prevH );

    // Interpolation of texture coordinates
    			return prevTCoords * weight + currentTextureCoords * ( 1.0 - weight );

    		#else // NO_PARALLAX

    			return vUv;

    		#endif

    	}
    #endif

    vec2 perturbUv( vec3 surfPosition, vec3 surfNormal, vec3 viewPosition ) {

    	vec2 texDx = dFdx( vUv );
    	vec2 texDy = dFdy( vUv );

    	vec3 vSigmaX = dFdx( surfPosition );
    	vec3 vSigmaY = dFdy( surfPosition );
    	vec3 vR1 = cross( vSigmaY, surfNormal );
    	vec3 vR2 = cross( surfNormal, vSigmaX );
    	float fDet = dot( vSigmaX, vR1 );

    	vec2 vProjVscr = ( 1.0 / fDet ) * vec2( dot( vR1, viewPosition ), dot( vR2, viewPosition ) );
    	vec3 vProjVtex;
    	vProjVtex.xy = texDx * vProjVscr.x + texDy * vProjVscr.y;
    	vProjVtex.z = dot( surfNormal, viewPosition );

    	return parallaxMap( vProjVtex );
    }

    void main() {

    	vec2 mapUv = perturbUv( -vViewPosition, normalize( vNormal ), normalize( vViewPosition ) );
    	gl_FragColor = texture2D( map, mapUv );

    }
  `,
}
